package download;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 某一个线程结束后，取消剩余最多，>10M,
 * 下载速度小于多少结束下载，重启线程
 */
public class Downloader {

    // 默认线程数量
    private static final int DEFAULT_THREAD_COUNT = 4;

    private final ThreadPoolExecutor executor;
    // 下载的文件对象
    private final DownloadFile file;
    private final Logger logger;
    private final DownloadTask[] tasks;

    private final String url;
    // 取消状态，如果有一个子线程出现异常，则取消整个下载任务
    private final AtomicBoolean canceled;
    // 线程数量
    private final int threadCount;
    // 文件大小
    private final long fileSize;

    // 开始时间
    private long beginTime;

    public Downloader(String url) throws IOException {
        this(url, DEFAULT_THREAD_COUNT);
    }

    public Downloader(String url, int threadCount) throws IOException {
        this.executor = new ThreadPoolExecutor(threadCount, threadCount,
                1, TimeUnit.HOURS, new SynchronousQueue<>());
        this.url = url;
        this.threadCount = threadCount;
        this.canceled = new AtomicBoolean(false);
        String storageLocation = url.substring(url.lastIndexOf('/')+1);
        this.logger = new Logger(storageLocation, url, threadCount);
        if (-1 == (fileSize = getFileSize())) {
            throw new RuntimeException("文件不存在");
        }
        this.file = new DownloadFile(storageLocation, fileSize, logger);
        tasks = new DownloadTask[threadCount];
        for (int i = 0; i < threadCount; i++) {
            tasks[i] = new DownloadTask(url, this, file, i);
        }
    }

    public void start() {
        boolean reStart = this.logger.getWroteSize() > 0;
        if (reStart) {
            file.setWroteSize(logger.getWroteSize());
            System.out.printf("* 继续上次下载进度[已下载：%.2fMB]：%s\n", logger.getWroteSize() / 1014.0 / 1024, url);
        } else {
            System.out.println("* 开始下载：" + url);
        }
        System.out.printf("* 文件大小：%.2fMB\n", fileSize / 1024.0 / 1024);
        this.beginTime = System.currentTimeMillis();
        // 分配线程下载
        dispatcher(reStart);
        logger.start();
        // 循环打印进度
        printDownloadProgress();
    }

    /**
     * 分配器，决定每个线程下载哪个区间的数据
     */
    private void dispatcher(boolean reStart) {
        // 每个线程要下载的数据量
        long blockSize = fileSize / threadCount;
        long lowerBound, upperBound;
        long[][] bounds = null;
        if (reStart) {
           bounds = logger.getBounds();
        }
        for (int i = 0; i < threadCount; i++) {
            if (reStart) {
                lowerBound = bounds[i][1];
                upperBound = bounds[i][2];
            } else {
                lowerBound = i * blockSize;
                upperBound = (i == threadCount - 1) ? fileSize - 1 : lowerBound + blockSize;
            }
            DownloadTask downloadTasks = tasks[i];
            downloadTasks.setIdx(lowerBound, upperBound);
            executor.execute(downloadTasks);
            logger.updateLog(i, 0, lowerBound, upperBound);
        }
    }

    /**
     * 循环打印进度，直到下载完毕，或任务被取消
     */
    private void printDownloadProgress() {
        long downloadedSize = file.getWroteSize();
        int i = 0;
        // 三秒前的下载量
        long lastSize = 0;
        while (!canceled.get() && downloadedSize < fileSize) {
            if (i++ % 4 == 3) {
                // 每3秒打印一次
                System.out.printf("下载进度：%.2f%%, 已下载：%.2fMB，当前速度：%.2fMB/s\n",
                        downloadedSize / (double)fileSize * 100 ,
                        downloadedSize / 1024.0 / 1024,
                        (downloadedSize - lastSize) / 1024.0 / 1024 / 3);
                lastSize = downloadedSize;
                i = 0;
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException ignore) {}
            downloadedSize = file.getWroteSize();
        }
        if (canceled.get()) {
            System.err.println("x 下载失败，任务已取消");
        } else {
            logger.clean();
            System.out.println("* 下载成功，本次用时"+ (System.currentTimeMillis() - beginTime) / 1000 +"秒");
        }
    }

    public void cancel() {
        for (int i = 0; i < threadCount; i++) {
            tasks[i].cancel();
        }
        this.canceled.set(true);
        logger.close();
        file.close();
    }

    /**
     * @return 要下载的文件的尺寸
     */
    private long getFileSize() {
        if (fileSize != 0) {
            return fileSize;
        }
        HttpURLConnection conn;
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setConnectTimeout(3000);
            conn.setReadTimeout(10000);
            conn.setRequestMethod("HEAD");
            conn.connect();
            System.out.println("* 连接服务器成功");
        } catch (MalformedURLException e) {
            throw new RuntimeException("URL错误");
        } catch (IOException e) {
            System.err.println("x 连接服务器失败["+ e.getMessage() +"]");
            return -1;
        }
        return conn.getContentLengthLong();
    }

    public static void main(String[] args) throws IOException {
//        new Downloader("https://mirror-1.truenetwork.ru/kali-images/kali-2020.3/kali-linux-2020.3-installer-amd64.iso", 4).start();
        new Downloader("http://10.8.0.1/file/nacos.tar").start();
//        new Downloader("http://222.90.192.228:3054/rrshare3.3.2.exe", 2).start();
    }
}

